#include "cas.h"

#include <fstream>
#include <iostream>
#include <OgreManualObject.h>
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgreTextureManager.h>
#include "util.h"


using namespace std;


map<string, shared_ptr<Cas>> Cas::filePathMap;


shared_ptr<Cas> Cas::get(string filePath, Cas::Type::Index index)
{
	shared_ptr<Cas> cas = filePathMap[filePath];
	if (cas.get() == NULL)
	{
		cas.reset(new Cas);
		cas->open(filePath, index);
		cas->prepare();
	}	
	if (cas->meshPtr.isNull())
	{
		cas = NULL;
	}
	return cas;
}


template<typename T>
void Cas::readLE(T* t)
{
	Util_readLE<T>(inputStream, t);
}


template<typename T>
void Cas::readLE(vector<T>& v, unsigned int length)
{
	Util_readLE<T>(inputStream, v, length);
}


void Cas::parseHeader()
{
	readLE<float>(&versionNumber);

	unsigned int chunkSize;
	readLE<unsigned int>(&chunkSize);

	inputStream.ignore(5 * 4);
	inputStream.ignore(3);
	inputStream.ignore(2 * 4);

	inputStream.read(signatureByteTriple2, 3);
}


void Cas::parseFileChunk()
{
	unsigned int fileChunk;
	readLE<unsigned int>(&fileChunk);

	unsigned int dummy;
	readLE<unsigned int>(&dummy);
}


void Cas::parseHierarchy()
{
	//if (versionNumber >= 3.195f)
	//{
		unsigned short n;
		readLE<unsigned short>((unsigned short*) &n);
		nSceneBones = n;
	//}
	//else
	//{
	//	unsigned char n;
	//	readLE<unsigned char>((unsigned char*) &n);
	//	nSceneBones = n;
	//}

	readLE<int>(hierarchy, nSceneBones);		
}


void Cas::parseTimeTicks()
{
	readLE<unsigned int>(&nFrames);		

	readLE<float>(timeTicks, nFrames);	
}


void Cas::parseBone()
{
    unsigned int nquatFrames = 0;
    unsigned int nanimFrames = 0;
    unsigned int nposeFrames = 0;    

	for (unsigned int i = 0; i < nSceneBones; i++)
	{
		Bone bone;

		unsigned int nCh;
		readLE<unsigned int>(&nCh);		

		char* boneName = new char[nCh];
		inputStream.read(boneName, nCh);
		bone.name = boneName;
		delete[] boneName;

		readLE<unsigned int>(&bone.quatFrames);
		readLE<unsigned int>(&bone.animFrames);
		readLE<unsigned int>(&bone.quatOffset);
		readLE<unsigned int>(&bone.animOffset);
		readLE<unsigned int>(&bone.zero);
           
        nquatFrames += bone.quatFrames;
        nanimFrames += bone.animFrames;
        nposeFrames++;       

		if (versionNumber > 3.195)
		{
			readLE<unsigned int>(&nCh);
			
			if (nCh == 1)
			{
				unsigned char c;
				readLE<unsigned char>(&c);
			}
			else
			{
				char* boneStr = new char[nCh];
				inputStream.read(boneStr, nCh);
				bone.str = boneStr;
				delete[] boneStr;
			}
		}
		bone.one = 1;

		bones.push_back(bone);
	}
		
	readLE<float>(quatFloats, 4 * nquatFrames);
	readLE<float>(animFloats, 3 * nanimFrames);
	readLE<float>(poseFloats, 3 * nposeFrames);
}


void Cas::parseMeshData()
{
	if (typeIndex == Type::RESOURCE || typeIndex == Type::NAVY)
	{
		nMeshes = 1;
		inputStream.ignore(3 * 4);

		unsigned int nChars;
		readLE<unsigned int>(&nChars);
		if (nChars == 26)
		{
			inputStream.ignore(26);
			inputStream.ignore(10);
			
			vector<float> floatVect1;
			readLE<float>(floatVect1, 9);
		}
		else
		{
			inputStream.seekg(-4, ios_base::cur);
		}
	}
	else
	{
		unsigned int chunkLength;
		readLE<unsigned int>(&chunkLength);

		inputStream.seekg(-4, ios_base::cur);

		if (chunkLength == 104)
		{
			inputStream.ignore(12);

			unsigned int nChars;
			readLE<unsigned int>(&nChars);

			inputStream.ignore(nChars + 10);

			vector<float> floatVect1;
			readLE<float>(floatVect1, 9);

			inputStream.ignore(24);

			readLE<unsigned int>(&nMeshes);
		}
		else
		{
			inputStream.ignore(26);

			readLE<unsigned int>(&nMeshes);
		}
	}

	for (unsigned int i = 0; i < nMeshes; i++)
	{
		Mesh mesh;

		unsigned int nChars;
		readLE<unsigned int>(&nChars);
		inputStream.ignore(nChars);

		inputStream.ignore(5);

		if (i == 0 && (typeIndex == Type::NAVY || typeIndex == Type::RESOURCE))
		{
			inputStream.ignore(9);
		}

		vector<float> floatVect2;
		readLE<float>(floatVect2, 7);

		unsigned short nVertex;
		readLE<unsigned short>(&nVertex);

		unsigned short nFaces;
		readLE<unsigned short>(&nFaces);

		unsigned short flagTVertex;
		readLE<unsigned short>(&flagTVertex);

		unsigned short flagVColors;
		readLE<unsigned short>(&flagVColors);

		if (typeIndex == Type::NAVY || typeIndex == Type::RESOURCE)
		{
			// Do nothing for now...
		}
		else
		{
			vector<int> boneIDs;
			readLE<int>(boneIDs, nVertex);
		}

		readLE<Ogre::Vector3>(mesh.vertexes, nVertex);

		readLE<Ogre::Vector3>(mesh.normals, nVertex);

		readLE<Face>(mesh.faces, nFaces);

		readLE<unsigned int>(&mesh.textureID);

		if (flagTVertex == 1)
		{
			readLE<Ogre::Vector2>(mesh.textureCoords, nVertex);
		}

		if (flagVColors == 1)
		{
			vector<char> vColors;
			readLE<char>(vColors, 2 * nVertex);
		}

		inputStream.ignore(4);

		meshes.push_back(mesh);
	}
}


void Cas::parseFooter()
{
	if (typeIndex == Type::NAVY)
	{
	}
	else if (typeIndex == Type::RESOURCE)
	{
	}
	else
	{
		vector<unsigned int> vect1;
		readLE<unsigned int>(vect1, 13);

		unsigned int footSize;
		readLE<unsigned int>(&footSize);

		vector<unsigned int> vect2;
		readLE<unsigned int>(vect2, 3);

		inputStream.ignore(1);

		unsigned int nCh = footSize - 75;
		char* tmpStr = new char[nCh];
		inputStream.read(tmpStr, nCh);
		string str = tmpStr;
		delete[] tmpStr;
	}
}


void Cas::parseRTWMeshes()
{
	readLE<unsigned int>(&nMeshes);

	for (unsigned int i = 0; i < nMeshes; i++)
	{
		unsigned int nChars;
		readLE<unsigned int>(&nChars);

		char* meshName = new char[nChars];
		inputStream.read(meshName, nChars);
		string temp = meshName;
		delete[] meshName;

		int boneID;
		bool flagBoneIDs;
		//if (chunkType == 1)
		//{
			inputStream.ignore(10);
			readLE<int>(&boneID);
			flagBoneIDs = true;
		//}
		//else
		//{
		//	boneID = 0;
		//	flagBoneIDs = false;
		//}

		inputStream.ignore(7 * 4);

		short nVertex;
		readLE<short>(&nVertex);

		short nFaces;
		readLE<short>(&nFaces);

		char flagTVertex;
		readLE<char>(&flagTVertex);

		char flagVColors;
		readLE<char>(&flagVColors);

		// Mesh
		{
			Mesh mesh;

			//if (chunkType == 3)
			//{
			//	readLE<Ogre::Vector3>(mesh.vertexes, nVertex);

			//	inputStream.ignore(52);

			//	vector<int> boneIDs;
			//	if (flagBoneIDs = false)
			//	{
			//		readLE<int>(boneIDs, nVertex);
			//	}
			//	else
			//	{
			//		for (int j = 0; j < nVertex; j++)
			//		{
			//			boneIDs.push_back(boneID);
			//		}
			//	}

			//	inputStream.ignore(52);
			//}
			//else
			{
				vector<int> boneIDs;
				if (flagBoneIDs = false)
				{
					readLE<int>(boneIDs, nVertex);
				}
				else
				{
					for (int j = 0; j < nVertex; j++)
					{
						boneIDs.push_back(boneID);
					}
				}
				readLE<Ogre::Vector3>(mesh.vertexes, nVertex);
			}

			readLE<Ogre::Vector3>(mesh.normals, nVertex);

			readLE<Face>(mesh.faces, nFaces);

			readLE<unsigned int>(&mesh.textureID);

			if (flagTVertex == 1)
			{			
				readLE<Ogre::Vector2>(mesh.textureCoords, nVertex);
			}

			if (flagVColors == 1)
			{
				vector<char> vColors;
				readLE<char>(vColors, 4 * nVertex);
			}

			meshes.push_back(mesh);
		}

		if (versionNumber > 3.16)
		{
			inputStream.ignore(4);
		}
		else
		{
			inputStream.ignore(1);
		}
	}
}

string Cas::parseRTWTextureFileName()
{
	string fileName;

	char c;
	inputStream.read(&c, 1);
	while (c != 0)
	{			
		fileName += c;
		inputStream.read(&c, 1);
	}

	return fileName;
}

void Cas::parseRTWTextures()
{
	unsigned int numTextures;
	readLE<unsigned int>(&numTextures);

	inputStream.ignore(5);

	for (unsigned int i = 0; i < numTextures; i++)
	{
		string textureFileName = parseRTWTextureFileName();
		//if (textureFileName[0] != '\\')
		//{
		//	textureFileName.insert(0, "\\");
		//}
		//if (textureFileName.find('.') == string::npos)
		//{
		//	textureFileName += ".dds";
		//}
		size_t index = textureFileName.find_last_of('\\');
		if (index != string::npos)
		{
			textureFileName.erase(0, index + 1);
		}
		textureFilePaths.push_back(textureFileName);
		inputStream.ignore(2 * 7 * 4);
		inputStream.ignore(6);
	}
}


void Cas::parseRTW()
{
	inputStream.seekg(0, std::ios::end);
	std::streampos fileLength = inputStream.tellg();
	inputStream.seekg(0);

	readLE<float>(&versionNumber);
	std::streampos offset = inputStream.tellg();

	while (offset < fileLength)
	{
		int chunkLength;
		readLE<int>(&chunkLength);

		int chunkType;
		readLE<int>(&chunkType);

		if (chunkType == 1)
		{
			parseRTWMeshes();
		}
		else if (chunkType == 5)
		{
			parseRTWTextures();
		}
		else
		{
			//std::cout << "Chunk type " << chunkType << " ignored" << endl;
		}

		offset += chunkLength;
		inputStream.seekg(offset);
	}
}


bool Cas::open(std::string filePath, Type::Index index)
{
	this->filePath = filePath;
	typeIndex = index;

	bool isSuccessful = false;

	inputStream.open(filePath, ifstream::binary);
	if (inputStream.is_open()) 
	{
		size_t index = filePath.find_last_of("\\/");
		basePath = filePath.substr(0, index);

		if (typeIndex != Type::RTW)
		{
			parseHeader();

			if(signatureByteTriple2[0] == 99)
			{
				typeIndex = Type::RESOURCE;
			}

			parseFileChunk();
			parseHierarchy();
			parseTimeTicks();
			parseBone();

			if (typeIndex != Type::ANIMATION)
			{
				parseMeshData();
			}
		}
		else
		{
			parseRTW();	
		}

		inputStream.close();

		isSuccessful = true;
	}
	else
	{
		cerr << "File not found: " << filePath << endl;
	}
	
	return isSuccessful;
}


void Cas::prepare()
{
	vector<string> materials(textureFilePaths.size());

	for (unsigned int i = 0; i < textureFilePaths.size(); i++)
	{
		string textureFileName = textureFilePaths[i] + ".dds";
		if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(textureFileName))
		{
			textureFilePaths[i] = textureFileName;
		}
		else if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(textureFilePaths[i]))
		{
			continue;
		}
		string materialName = "Material " + textureFilePaths[i];

		Ogre::ResourceManager::ResourceCreateOrRetrieveResult result = Ogre::MaterialManager::getSingleton().createOrRetrieve(materialName, "Campaign");
		Ogre::MaterialPtr materialPtr = result.first;
		if (result.second == true)
		{
			Ogre::Technique* technique = materialPtr->getTechnique(0); 

			technique->setCullingMode(Ogre::CULL_NONE);
			//technique->setTextureFiltering(Ogre::TFO_TRILINEAR);

			Ogre::Pass* pass = technique->getPass(0);
			pass->createTextureUnitState(textureFilePaths[i]);
			//pass->setTextureAnisotropy(2);
		}
		materials[i] = materialPtr->getName();
	}
	
	Ogre::ManualObject* casObject = OGRE_NEW Ogre::ManualObject(filePath + " ManualObject");	

	bool anyMesh = false;
	for (unsigned int i = 0; i < meshes.size(); i++)
	{
		Mesh mesh = meshes[i];
		if (mesh.vertexes.size() == 0)
		{
			continue;
		}
		string materialName = materials[mesh.textureID];
		if (materialName.size() == 0)
		{
			continue;
		}

		casObject->begin(materialName, Ogre::RenderOperation::OT_TRIANGLE_LIST);
		//casObject->begin("BaseWhiteNoLighting", Ogre::RenderOperation::OT_TRIANGLE_LIST);
		for (unsigned int j = 0; j < mesh.vertexes.size(); j++)
		{
			casObject->position(mesh.vertexes[j].x, mesh.vertexes[j].y, -mesh.vertexes[j].z);
			casObject->normal(  mesh.normals [j].x, mesh.normals [j].y, -mesh.normals [j].z);

			casObject->textureCoord(mesh.textureCoords[j].x, mesh.textureCoords[j].y);
		}

		for (unsigned int j = 0; j < mesh.faces.size(); j++)
		{
			casObject->triangle(mesh.faces[j].a, mesh.faces[j].c, mesh.faces[j].b);
		}
		casObject->end();
		anyMesh = true;
	}

	if (anyMesh)
	{
		meshPtr = casObject->convertToMesh(filePath + " Mesh");
	}
}

string Cas::getMeshName()
{
	string meshName;
	if (!meshPtr.isNull())
	{
		meshName = meshPtr->getName();
	}
	return meshName;
}
